In this project, we tested different classification model for facial emotion recognition. Our group tried out six different machine learning algorithms, trained them on the given data set, cross-validated to find the optimized parameters, and provided fair evaluation for all the models. The evaluation process considered the prediction error (and accuracy), the area under the ROC curve (or AUC), and running time (testing and training) to choose the most improved model.

if(!require("EBImage")){
  install.packages("BiocManager")
  BiocManager::install("EBImage")
}
程辑包‘EBImage’是用R版本4.0.3 来建造的
if(!require("R.matlab")){
  install.packages("R.matlab")
}
if(!require("readxl")){
  install.packages("readxl")
}
if(!require("dplyr")){
  install.packages("dplyr")
}
if(!require("readxl")){
  install.packages("readxl")
}
if(!require("ggplot2")){
  install.packages("ggplot2")
}
if(!require("caret")){
  install.packages("caret")
}
if(!require("glmnet")){
  install.packages("glmnet")
}
if(!require("WeightedROC")){
  install.packages("WeightedROC")
}
if(!require("e1071")){
  install.packages("e1071")
}
if(!require("xgboost")){
  install.packages("xgboost")
}
if(!require("randomForest")){
  install.packages("randomForest")
}
library(R.matlab)
library(readxl)
library(dplyr)
library(EBImage)
library(ggplot2)
library(caret)
library(glmnet)
library(WeightedROC)
library(e1071)
library(xgboost)
library(randomForest)

Step 0 set work directories

set.seed(2020)
# setwd("~/Project3-FacialEmotionRecognition/doc")
# here replace it with your own path or manually set it in RStudio to where this rmd file is located. 
# use relative path for reproducibility

Provide directories for training images. Training images and Training fiducial points will be in different subfolders.

train_dir <- "../data/train_set/" # This will be modified for different data sets.
train_image_dir <- paste(train_dir, "images/", sep="")
train_pt_dir <- paste(train_dir,  "points/", sep="")
train_label_path <- paste(train_dir, "label.csv", sep="") 

Step 1: set up controls for evaluation experiments.

In this chunk, we have a set of controls for the evaluation experiments.

run.cv <- FALSE # run cross-validation on the training set
run.cv.baseline <- FALSE # run cross-validation on the gbm baseline
sample.reweight <- TRUE # run sample reweighting in model training
K <- 5  # number of CV folds
run.feature.train <- TRUE # process features for training set
run.test <- TRUE # run evaluation on an independent test set
run.feature.test <- TRUE # process features for test set
run.cv.svm <- FALSE
run.test.svm <- TRUE

Using cross-validation or independent test set evaluation, we compare the performance of models with different specifications.


#gbm parameters tuning:
n.trees = c(10,50,100,200)
shrinkage = c(0.01,0.05,0.1,0.15)

#svm parameters tuning:
cost = c(0.0000001,0.000001,0.00001,0.0001,0.001,0.01,1)
model_labels_svm = paste("SVM with cost =", cost)

#xgboost parameters tuning
params <- list(booster = "gbtree", objective = "binary:logistic", 
                 eta=0.3, gamma=0, max_depth=6, min_child_weight=1, 
                 subsample=1, colsample_bytree=1)

#random forest parameters tuning
ntrees <- 128 
#according to a paper by Thais Mayumi Oshiro, Pedro Santoro Perez, and Jos ́e Augusto Baranauska,
# the AUC gain for increasing number of trees is minimal after 128, 
# after observing a multitude of their datasets

Step 2: import data and train-test split

#train-test split
info <- read.csv(train_label_path)
n <- nrow(info)
n_train <- round(n*(4/5), 0)
train_idx <- sample(info$Index, n_train, replace = F)
test_idx <- setdiff(info$Index, train_idx)

If you choose to extract features from images, such as using Gabor filter, R memory will exhaust all images are read together. The solution is to repeat reading a smaller batch(e.g 100) and process them.

#function to read fiducial points
#input: index
#output: matrix of fiducial points corresponding to the index
readMat.matrix <- function(index){
     return(round(readMat(paste0(train_pt_dir, sprintf("%04d", index), ".mat"))[[1]],0))
}
#load fiducial points
fiducial_pt_list <- lapply(1:n_files, readMat.matrix)
save(fiducial_pt_list, file="../output/fiducial_pt_list.RData")

Fiducial points are stored in matlab format. In this step, we read them and store them in a list.

#function to read fiducial points
#input: index
#output: matrix of fiducial points corresponding to the index
readMat.matrix <- function(index){
     return(round(readMat(paste0(train_pt_dir, sprintf("%04d", index), ".mat"))[[1]],0))
}

#load fiducial points
fiducial_pt_list <- lapply(1:n_files, readMat.matrix)
save(fiducial_pt_list, file="../output/fiducial_pt_list.RData")

Step 3: construct features and responses

Figure1

Figure1

feature.R should be the wrapper for all your feature engineering functions and options. The function feature( ) should have options that correspond to different scenarios for your project and produces an R object that contains features and responses that are required by all the models you are going to evaluate later.

source("../lib/feature.R")
tm_feature_train <- NA
if(run.feature.train){
  tm_feature_train <- system.time(dat_train <- feature(fiducial_pt_list, train_idx))
  save(dat_train, file="../output/feature_train.RData")
}else{
  load(file="../output/feature_train.RData")
}

tm_feature_test <- NA
if(run.feature.test){
  tm_feature_test <- system.time(dat_test <- feature(fiducial_pt_list, test_idx))
  save(dat_test, file="../output/feature_test.RData")
}else{
  load(file="../output/feature_test.RData")
}

Step 4: Train a classification model with training features and responses

Call the train model and test model from library.

train.R and test.R should be wrappers for all your model training steps and your classification/prediction steps.

source("../lib/train.R") 
source("../lib/test.R")

source("../lib/train_gbm.R")
source("../lib/test_gbm.R")

source("../lib/train_SVM.R") 
source("../lib/test_SVM.R")

source("../lib/fit_train_xgboost.R")
source("../lib/fit_train_randomforest.R")

Model selection with cross-validation

  • Do model selection by choosing among different values of training model parameters.

Baseline Model + Baseline/GBM

feature_train = as.matrix(dat_train[, -6007])
label_train = as.integer(dat_train$label) 

source("../lib/cross_validation.R")
source("../lib/cross_validation_SVM.R")
source("../lib/cv_gbm.R")

if(run.cv.baseline){  
  
  mean_error_cv <- matrix(0, nrow = length(n.trees), ncol = length(shrinkage))
  sd_error_cv <- matrix(0, nrow = length(n.trees), ncol = length(shrinkage))
  mean_auc_cv <- matrix(0, nrow = length(n.trees), ncol = length(shrinkage))
  sd_auc_cv <- matrix(0, nrow = length(n.trees), ncol = length(shrinkage))

  for(i in 1:length(n.trees)){
    cat("n.trees =", n.trees[i],"\n")
  for(k in 1:length(shrinkage)){
    cat("shrinkage =", shrinkage[k],"\n")
    
res_cv_gbm <- cv_gbm(features = feature_train, labels = label_train, K,  n.trees = n.trees[i],shrinkage = shrinkage[k],reweight = sample.reweight)
      
    mean_error_cv[i,k]<-res_cv_gbm[1]
     sd_error_cv[i,k]<-res_cv_gbm[2]
      mean_auc_cv[i,k]<-res_cv_gbm[3]
       sd_auc_cv[i,k]<-res_cv_gbm[4]
      
    save(mean_error_cv, file="../output/mean_error_cv.RData")
    save(sd_error_cv, file="../output/sd_error_cv.RData")
    save(mean_auc_cv, file="../output/mean_auc_cv.RData")
    save(sd_auc_cv, file="../output/sd_auc_cv.RData") 
    }}
 } else{
  load("../output/mean_error_cv.RData")
  load("../output/sd_error_cv.RData")   
  load("../output/mean_auc_cv.RData")
  load("../output/sd_auc_cv.RData")
    }

if(run.cv){
  res_cv <- matrix(0, nrow = length(lmbd), ncol = 4)
  for(i in 1:length(lmbd)){
    cat("lambda = ", lmbd[i], "\n")
    res_cv[i,] <- cv.function(features = feature_train, labels = label_train, K, 
                              l = lmbd[i], reweight = sample.reweight)
  save(res_cv, file="../output/res_cv.RData")
  }
}else{
  load("../output/res_cv.RData")
}
library(tidyr)

df_mean_error=data.frame(mean_error_cv)%>%
setNames(shrinkage)%>%
mutate(n.trees=n.trees)%>%
gather(shrinkage,mean_error,`0.01`:`0.15`)

df_sd_error=data.frame(sd_error_cv)%>%
setNames(shrinkage)%>%
mutate(n.trees=n.trees)%>%
gather(shrinkage,sd_error,`0.01`:`0.15`)

df_mean_auc=data.frame(mean_auc_cv)%>%
setNames(shrinkage)%>%
mutate(n.trees=n.trees)%>%
gather(shrinkage,mean_auc,`0.01`:`0.15`)

df_sd_auc=data.frame(sd_auc_cv)%>%
setNames(shrinkage)%>%
mutate(n.trees=n.trees)%>%
gather(shrinkage,sd_auc,`0.01`:`0.15`)

res_cv_gbm <- df_mean_error%>%mutate(sd_error=df_sd_error$sd_error,
                                     mean_auc=df_mean_auc$mean_auc,
                                     sd_auc=df_sd_auc$sd_auc)
save(res_cv_gbm,file = "../output/res_cv_gbm.RData")
  • SVM
if(run.cv.svm){
  res_cv_svm <- matrix(0, nrow = length(cost), ncol = 4)
  for(i in 1:length(cost)){
    cat("cost= ", cost[i], "\n")
    res_cv_svm[i,] <- svm_cv(features = feature_train, labels = label_train, K, cost=cost[i], reweight = sample.reweight)
  save(res_cv_svm, file="../output/res_cv_svm.RData")
  }
}else{
  load("../output/res_cv_svm.RData")
}

Improved Model + XGBoost


source("../lib/xgboost_cv.R")
feature_train = as.matrix(dat_train[, -6007])
label_train = as.integer(dat_train$label) 
label_train_xgb <- label_train
label_train_xgb[label_train_xgb == 2] <- 0

set_rounds  <- 50
K <- 5

new_params <- cv_xgboost(params, feature_train, label_train_xgb, set_rounds, K)

save(new_params, file="../output/res_cv_xgboost.RData")

Visualize cross-validation results.

load("../output/res_cv_gbm.RData")


if(run.cv.baseline){
  p1 <- res_cv_gbm %>% 
    ggplot(aes(x = n.trees, y = mean_error,
               ymin = mean_error - sd_error, ymax = mean_error  +sd_error)) + 
    geom_crossbar() +
    facet_wrap(~shrinkage) +
    theme(axis.text.x = element_text(angle = 90, hjust = 1))
  
  p2 <- res_cv_gbm %>% 
    ggplot(aes(x = n.trees, y = mean_auc,
               ymin = mean_auc - sd_auc, ymax = mean_auc + sd_auc)) +     facet_wrap(~shrinkage) +
    geom_crossbar() +
    theme(axis.text.x = element_text(angle = 90, hjust = 1))
  
  print(p1)
  print(p2)
}

res_cv_svm <- as.data.frame(res_cv_svm) 
colnames(res_cv_svm) <- c("mean_error", "sd_error", "mean_AUC", "sd_AUC")
res_cv_svm$k = as.factor(cost)
run.cv.svm <- TRUE
if(run.cv.svm){
  p1 <- res_cv_svm %>% 
    ggplot(aes(x = as.factor(cost), y = mean_error,
               ymin = mean_error - sd_error, ymax = mean_error + sd_error)) + 
    geom_crossbar() +
    theme(axis.text.x = element_text(angle = 90, hjust = 1))
  
  p2 <- res_cv_svm %>% 
    ggplot(aes(x = as.factor(cost), y = mean_AUC,
               ymin = mean_AUC - sd_AUC, ymax = mean_AUC + sd_AUC)) + 
    geom_crossbar() +
    theme(axis.text.x = element_text(angle = 90, hjust = 1))
  
  print(p1)
  print(p2)
}
  • Choose the "best" parameter value for baseline model
tm_test_xgb = NA
feature_test <- as.matrix(dat_test[, -6007])
if(run.test){
  load(file="../output/fit_train_xgb.RData")
  tm_test_xgb <- system.time({label_pred_xgb <- predict(fit_train_xgb, feature_test, pred.type = 'class');
                          label_pred_xgb[label_pred_xgb >= 0.5] <- 1;
                          label_pred_xgb[label_pred_xgb < 0.5] <-  0;
                          prob_pred_xgb <- predict(fit_train_xgb, feature_test, pred.type = 'response')})
}
  • Choose "best" number of trees and mtry for random forest model
source("../lib/rf_param_choice.R")
feature_train = as.matrix(dat_train[, -6007])
label_train = as.integer(dat_train$label) 
init_mtry <- sqrt(ncol(dat_train))

rf_opt_tree <- param_choice_rf(feature_train = feature_train, label_train =  label_train, mtry = init_mtry, ntree = ntrees)

save(rf_opt_tree, file = "../output/res_oob_rf.RData")

Train different models

  • Train the baseline model with the entire training set using the selected model (model parameter) via cross-validation.
# training weights
weight_train <- rep(NA, length(label_train))
for (v in unique(label_train)){
  weight_train[label_train == v] = 0.5 * length(label_train) / length(label_train[label_train == v])
}
if (sample.reweight){
  tm_train_baseline <- system.time(fit_train_baseline <- train_gbm(feature_train, label_train, w = weight_train,best_n.trees, best_shrinkage))
} else {
  tm_train_baseline <- system.time(fit_train_baseline <- train_gbm(feature_train, label_train, w = NULL, best_n.trees, best_shrinkage))
}
save(fit_train_baseline, file="../output/fit_train_baseline.RData")
  • Train the SVM model with the entire training set using the selected model (model parameter) via cross-validation.
weight_train <- rep(NA, length(label_train))
for (v in unique(label_train)){
  weight_train[label_train == v] = 0.5 * length(label_train) / length(label_train[label_train == v])
}
if (sample.reweight){
  tm_train_svm <- system.time(fit_train_svm <-svm_train(feature_train, label_train, w = weight_train, best_cost))
} else {
  tm_train_svm <- system.time(fit_train_svm <-svm_train(feature_train, label_train, w = NULL, best_cost))
}
save(fit_train_svm, file="../output/fit_train_svm.RData")
  • Train the XGBoost model with optimal parameters

xgb_train_time <- system.time(fit_train_xgb <- xgboost_train(features = feature_train, labels = label_train_xgb, params = new_params, rounds =  set_rounds))

save(fit_train_xgb, file="../output/fit_train_xgb.RData")
  • Train the Random Forest model with optimal parameters
load(file = "../output/res_oob_rf.RData")
rf_train_time <- system.time(fit_train_randomforest <- rf_train(feature_train = feature_train, label_train = label_train, mtry = rf_opt_tree$mtry, ntree = rf_opt_tree$ntree))
save(fit_train_randomforest, file="../output/fit_train_rf.RData")
  • train PCA + LDA

#PCA for training features
source("../lib/train_PCA.R")

# make dat_train a numeric data frame
dat_train.new <- matrix(0, ncol = ncol(dat_train) - 1, nrow = nrow(dat_train))
for (i in 1:(ncol(dat_train) - 1)) {
dat_train.new[,i] <- as.numeric(dat_train[[i]])
}
dat_train.new <- as.data.frame(dat_train.new)

#PCA for training features
tm_train_pca <- system.time(fit_train_pca <- train_pca(dat_train.new))

save(fit_train_pca, file="../output/pca_train.RData")

# determine the important principle components
screeplot(fit_train_pca, type = "l")

# The proportion of variance for first 300 PCs
sum((fit_train_pca$sdev[1:300])^2) / sum((fit_train_pca$sdev)^2)

Therefore, we can choose the first 300 principle components because they explain most of the total variance, which is around 99.9%.

# get the features of principle components with emotion index
dat_train_pca <- data.frame(fit_train_pca$x[,1:300], emotion_idx = dat_train[,6007])

Use trained PCA model to test data

source("../lib/test_PCA.R")
dat_test.new <- dat_test
colnames(dat_test.new) <- c(colnames(dat_train.new), "emotion_idx")

tm_test_pca <- system.time(dat_test.new <- test_pca(fit_train_pca, dat_test.new))

#features of testing principle components with the emotion index
dat_test_pca <- data.frame(dat_test.new[,1:300], emotion_idx = dat_test[,6007])

save(dat_train_pca, file="../output/feature_train_pca.RData")
save(dat_test_pca, file="../output/feature_test_pca.RData")

Apply LDA model

#load("../output/feature_train_pca.RData")

#train LDA model
source("../lib/train_LDA.R")
tm_train_lda <- system.time(fit_train_lda <- train_lda(dat_train_pca))
save(fit_train_lda, file="../output/LDA_train.RData")

#training accuracy in LDA model
source("../lib/test_LDA.R")
pred_train_lda <- test_lda(fit_train_lda, dat_train_pca)
accu_train_lda <- mean(pred_train_lda == dat_train_pca$emotion_idx)

cat("The trainig accuracy of model LDA", "is", accu_train_lda*100, "%.\n")
cat("Time for training model LDA = ", tm_train_lda[1], "s \n")

Step 5: Run test on test images

+Baseline model

tm_test_baseline = NA
feature_test <- as.matrix(dat_test[, -6007])
if(run.test){
  load(file="../output/fit_train_baseline.RData")
  tm_test_baseline <- system.time({label_pred_baseline <- as.integer(test_gbm(fit_train_baseline,feature_test,best_n.trees, best_shrinkage, pred.type = 'link')); 
                          prob_pred_baseline <- test_gbm(fit_train_baseline, feature_test,best_n.trees, best_shrinkage, pred.type = 'response')})
}

+SVM model

tm_test = NA
feature_test <- as.matrix(dat_test[, -6007])
if(run.test.svm){
  load(file="../output/fit_train_svm.RData")
  tm_test_svm <- system.time({
    label_pred_svm <- as.integer(svm_test(fit_train_svm, feature_test, pred.type = 'class')); 
    prob_pred_svm <- svm_test(fit_train_svm, feature_test, pred.type = 'response')})
}

+XGBoost

tm_test_xgb = NA
feature_test <- as.matrix(dat_test[, -6007])
if(run.test){
  load(file="../output/fit_train_xgb.RData")
  tm_test_xgb <- system.time({label_pred_xgb <- predict(fit_train_xgb, feature_test, pred.type = 'class');
                          label_pred_xgb[label_pred_xgb >= 0.5] <- 1;
                          label_pred_xgb[label_pred_xgb < 0.5] <-  0;
                          prob_pred_xgb <- predict(fit_train_xgb, feature_test, pred.type = 'response')})
}

+Random Forest

tm_test_rf = NA
feature_test <- as.matrix(dat_test[, -6007])
if(run.test){
  load(file="../output/fit_train_rf.RData")
  tm_test_rf <- system.time({label_pred_rf <- predict(fit_train_randomforest, feature_test, pred.type = 'class');
                          prob_pred_rf <- predict(fit_train_randomforest, feature_test, type = 'prob')})
} 

+LDA

source("../lib/test_LDA.R")
load(file = "../output/feature_test_pca.RData")
load(file="../output/LDA_train.RData")
tm_test_lda <- system.time(pred_lda <- test_lda(fit_train_lda, dat_test_pca))

Evaluation

*Baseline Model

## reweight the test data to represent a balanced label distribution
label_test <- as.integer(dat_test$label)
weight_test <- rep(NA, length(label_test))
for (v in unique(label_test)){
  weight_test[label_test == v] = 0.5 * length(label_test) / length(label_test[label_test == v])
}

accu_baseline <- mean(label_pred_baseline == label_test)
tpr.fpr.baseline <- WeightedROC(prob_pred_baseline, label_test, weight_test)
auc_baseline <- WeightedAUC(tpr.fpr.baseline)


cat("The accuracy of model GBM: with n.trees=",best_n.trees,"and shrinkage =", best_shrinkage, "is", accu_baseline*100, "%.\n")
cat("The AUC of model GBM: with n.trees=", best_n.trees,"and shrinkage =", best_shrinkage, "is", auc_baseline, ".\n")

*SVM

label_test <- as.integer(dat_test$label)
weight_test <- rep(NA, length(label_test))
for (v in unique(label_test)){
  weight_test[label_test == v] = 0.5 * length(label_test) / length(label_test[label_test == v])
}

accu_svm <- mean(label_pred_svm == label_test)
tpr.fpr.svm <- WeightedROC(prob_pred_svm, label_test, weight_test)
auc_svm <- WeightedAUC(tpr.fpr.svm)
cat("The accuracy of model:", model_labels_svm[which.min(res_cv_svm$mean_error)], "is", accu_svm*100, "%.\n")
cat("The AUC of model:", model_labels_svm[which.min(res_cv_svm$mean_error)], "is", auc_svm, ".\n")

*XGBoost

label_test <- as.integer(dat_test$label)
label_test_xgb <- label_test
label_test_xgb[label_test_xgb==2] = 0
accu_xgb <- mean((label_pred_xgb == label_test_xgb))
tpr.fpr_xgb <- WeightedROC(prob_pred_xgb, label_test_xgb)
auc_xgb <- WeightedAUC(tpr.fpr_xgb)


cat("The accuracy of the XGBoost model:", "is", accu_xgb*100, "%.\n")
cat("The AUC of the XGBoost model:", "is", auc_xgb, ".\n")

*Random Forest

label_test <- as.integer(dat_test$label)
accu_rf <- mean(label_pred_rf == label_test)
tpr.fpr.rf <- WeightedROC(prob_pred_rf[,2], label_test)
auc_rf <- WeightedAUC(tpr.fpr.rf)


cat("The accuracy of the Random Forest model:", "is", accu_rf*100, "%.\n")
cat("The AUC of the Random Forest model:", "is", auc_rf, ".\n")

*LDA

accu_lda <- mean(dat_test_pca$emotion_idx == pred_lda)
label_lda <- as.numeric(dat_test_pca$emotion_idx)
tpr.fpr.lda <- WeightedROC(as.numeric(pred_lda), label_lda)
auc_lda <- WeightedAUC(tpr.fpr.lda)

cat("The accuracy of the PCA+LDA model:", "is", accu_lda*100, "%.\n")
cat("The AUC of the PCA+LDA model:", "is", auc_lda, ".\n")

Summarize Running Time

Prediction performance matters, so does the running times for constructing features and for training the model, especially when the computation resource is limited.

*Baseline Model

cat("Time for constructing training features=", tm_feature_train[1], "s \n")
cat("Time for constructing testing features=", tm_feature_test[1], "s \n")
cat("Time for training model=", tm_train_baseline[1], "s \n") 
cat("Time for testing model=", tm_test_baseline[1], "s \n")

*SVM

cat("Time for constructing training features=", tm_feature_train[1], "s \n")
cat("Time for constructing testing features=", tm_feature_test[1], "s \n")
cat("Time for training model=", tm_train_svm[1], "s \n") 
cat("Time for testing model=", tm_test_svm[1], "s \n")

*XGBoost

cat("Time for constructing training features=", tm_feature_train[1], "s \n")
cat("Time for constructing testing features=", tm_feature_test[1], "s \n")
cat("Time for training model=", xgb_train_time[1], "s \n") 
cat("Time for testing model=", tm_test_xgb[1], "s \n")

*Random Forest

cat("Time for constructing training features=", tm_feature_train[1], "s \n")
cat("Time for constructing testing features=", tm_feature_test[1], "s \n")
cat("Time for training random forest model=", rf_train_time[1], "s \n")
cat("Time for testing random forest model=", tm_test_rf[1], "s \n")
cat("Time for constructing training features=", tm_feature_train[1], "s \n")
cat("Time for constructing testing features=", tm_feature_test[1], "s \n")

cat("Time for training PCA =", tm_train_pca[1], "s \n")
cat("Time for testing PCA =", tm_test_pca[1], "s \n")
cat("Time for training model LDA = ", tm_train_lda[1], "s \n")
cat("Time for testing model LDA = ",tm_test_lda[1], "s \n")

Reference

LS0tCnRpdGxlOiAiV29ya2luZ19NYWluIgphdXRob3I6ICJEYWl6eSBMYW0sIFBldGVyIEt3YXVrLCBRaXpoZW4gWWFuZywgRWxsZW4gQ2hlbiwgRGFyeWwgS293IgpvdXRwdXQ6CiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAotLS0KCkluIHRoaXMgcHJvamVjdCwgd2UgdGVzdGVkIGRpZmZlcmVudCBjbGFzc2lmaWNhdGlvbiBtb2RlbCBmb3IgZmFjaWFsIGVtb3Rpb24gcmVjb2duaXRpb24uIE91ciBncm91cCB0cmllZCBvdXQgc2l4IGRpZmZlcmVudCBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobXMsIHRyYWluZWQgdGhlbSBvbiB0aGUgZ2l2ZW4gZGF0YSBzZXQsIGNyb3NzLXZhbGlkYXRlZCB0byBmaW5kIHRoZSBvcHRpbWl6ZWQgcGFyYW1ldGVycywgYW5kIHByb3ZpZGVkIGZhaXIgZXZhbHVhdGlvbiBmb3IgYWxsIHRoZSBtb2RlbHMuIFRoZSBldmFsdWF0aW9uIHByb2Nlc3MgY29uc2lkZXJlZCB0aGUgcHJlZGljdGlvbiBlcnJvciAoYW5kIGFjY3VyYWN5KSwgdGhlIGFyZWEgdW5kZXIgdGhlIFJPQyBjdXJ2ZSAob3IgQVVDKSwgYW5kIHJ1bm5pbmcgdGltZSAodGVzdGluZyBhbmQgdHJhaW5pbmcpIHRvIGNob29zZSB0aGUgbW9zdCBpbXByb3ZlZCBtb2RlbC4KCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CmlmKCFyZXF1aXJlKCJFQkltYWdlIikpewogIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikKICBCaW9jTWFuYWdlcjo6aW5zdGFsbCgiRUJJbWFnZSIpCn0KaWYoIXJlcXVpcmUoIlIubWF0bGFiIikpewogIGluc3RhbGwucGFja2FnZXMoIlIubWF0bGFiIikKfQppZighcmVxdWlyZSgicmVhZHhsIikpewogIGluc3RhbGwucGFja2FnZXMoInJlYWR4bCIpCn0KCmlmKCFyZXF1aXJlKCJkcGx5ciIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJkcGx5ciIpCn0KaWYoIXJlcXVpcmUoInJlYWR4bCIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJyZWFkeGwiKQp9CgppZighcmVxdWlyZSgiZ2dwbG90MiIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikKfQoKaWYoIXJlcXVpcmUoImNhcmV0IikpewogIGluc3RhbGwucGFja2FnZXMoImNhcmV0IikKfQoKaWYoIXJlcXVpcmUoImdsbW5ldCIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJnbG1uZXQiKQp9CgppZighcmVxdWlyZSgiV2VpZ2h0ZWRST0MiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiV2VpZ2h0ZWRST0MiKQp9CgppZighcmVxdWlyZSgiZTEwNzEiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiZTEwNzEiKQp9CgppZighcmVxdWlyZSgieGdib29zdCIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJ4Z2Jvb3N0IikKfQoKaWYoIXJlcXVpcmUoInJhbmRvbUZvcmVzdCIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJyYW5kb21Gb3Jlc3QiKQp9CgpsaWJyYXJ5KFIubWF0bGFiKQpsaWJyYXJ5KHJlYWR4bCkKbGlicmFyeShkcGx5cikKbGlicmFyeShFQkltYWdlKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkoZ2xtbmV0KQpsaWJyYXJ5KFdlaWdodGVkUk9DKQpsaWJyYXJ5KGUxMDcxKQpsaWJyYXJ5KHhnYm9vc3QpCmxpYnJhcnkocmFuZG9tRm9yZXN0KQpgYGAKCiMjIyBTdGVwIDAgc2V0IHdvcmsgZGlyZWN0b3JpZXMKYGBge3Igd2tkaXIsIGV2YWw9RkFMU0V9CnNldC5zZWVkKDIwMjApCiMgc2V0d2QoIn4vUHJvamVjdDMtRmFjaWFsRW1vdGlvblJlY29nbml0aW9uL2RvYyIpCiMgaGVyZSByZXBsYWNlIGl0IHdpdGggeW91ciBvd24gcGF0aCBvciBtYW51YWxseSBzZXQgaXQgaW4gUlN0dWRpbyB0byB3aGVyZSB0aGlzIHJtZCBmaWxlIGlzIGxvY2F0ZWQuIAojIHVzZSByZWxhdGl2ZSBwYXRoIGZvciByZXByb2R1Y2liaWxpdHkKYGBgCgpQcm92aWRlIGRpcmVjdG9yaWVzIGZvciB0cmFpbmluZyBpbWFnZXMuIFRyYWluaW5nIGltYWdlcyBhbmQgVHJhaW5pbmcgZmlkdWNpYWwgcG9pbnRzIHdpbGwgYmUgaW4gZGlmZmVyZW50IHN1YmZvbGRlcnMuIApgYGB7cn0KdHJhaW5fZGlyIDwtICIuLi9kYXRhL3RyYWluX3NldC8iICMgVGhpcyB3aWxsIGJlIG1vZGlmaWVkIGZvciBkaWZmZXJlbnQgZGF0YSBzZXRzLgp0cmFpbl9pbWFnZV9kaXIgPC0gcGFzdGUodHJhaW5fZGlyLCAiaW1hZ2VzLyIsIHNlcD0iIikKdHJhaW5fcHRfZGlyIDwtIHBhc3RlKHRyYWluX2RpciwgICJwb2ludHMvIiwgc2VwPSIiKQp0cmFpbl9sYWJlbF9wYXRoIDwtIHBhc3RlKHRyYWluX2RpciwgImxhYmVsLmNzdiIsIHNlcD0iIikgCmBgYAoKIyMjIFN0ZXAgMTogc2V0IHVwIGNvbnRyb2xzIGZvciBldmFsdWF0aW9uIGV4cGVyaW1lbnRzLgoKSW4gdGhpcyBjaHVuaywgd2UgaGF2ZSBhIHNldCBvZiBjb250cm9scyBmb3IgdGhlIGV2YWx1YXRpb24gZXhwZXJpbWVudHMuIAoKKyAoVC9GKSBjcm9zcy12YWxpZGF0aW9uIG9uIHRoZSB0cmFpbmluZyBzZXQKKyAoVC9GKSByZXdlaWdodGluZyB0aGUgc2FtcGxlcyBmb3IgdHJhaW5pbmcgc2V0IAorIChudW1iZXIpIEssIHRoZSBudW1iZXIgb2YgQ1YgZm9sZHMKKyAoVC9GKSBwcm9jZXNzIGZlYXR1cmVzIGZvciB0cmFpbmluZyBzZXQKKyAoVC9GKSBydW4gZXZhbHVhdGlvbiBvbiBhbiBpbmRlcGVuZGVudCB0ZXN0IHNldAorIChUL0YpIHByb2Nlc3MgZmVhdHVyZXMgZm9yIHRlc3Qgc2V0CgpgYGB7ciBleHBfc2V0dXB9CnJ1bi5jdiA8LSBGQUxTRSAjIHJ1biBjcm9zcy12YWxpZGF0aW9uIG9uIHRoZSB0cmFpbmluZyBzZXQKcnVuLmN2LmJhc2VsaW5lIDwtIEZBTFNFICMgcnVuIGNyb3NzLXZhbGlkYXRpb24gb24gdGhlIGdibSBiYXNlbGluZQpzYW1wbGUucmV3ZWlnaHQgPC0gVFJVRSAjIHJ1biBzYW1wbGUgcmV3ZWlnaHRpbmcgaW4gbW9kZWwgdHJhaW5pbmcKSyA8LSA1ICAjIG51bWJlciBvZiBDViBmb2xkcwpydW4uZmVhdHVyZS50cmFpbiA8LSBUUlVFICMgcHJvY2VzcyBmZWF0dXJlcyBmb3IgdHJhaW5pbmcgc2V0CnJ1bi50ZXN0IDwtIFRSVUUgIyBydW4gZXZhbHVhdGlvbiBvbiBhbiBpbmRlcGVuZGVudCB0ZXN0IHNldApydW4uZmVhdHVyZS50ZXN0IDwtIFRSVUUgIyBwcm9jZXNzIGZlYXR1cmVzIGZvciB0ZXN0IHNldApydW4uY3Yuc3ZtIDwtIEZBTFNFCnJ1bi50ZXN0LnN2bSA8LSBUUlVFCgpgYGAKClVzaW5nIGNyb3NzLXZhbGlkYXRpb24gb3IgaW5kZXBlbmRlbnQgdGVzdCBzZXQgZXZhbHVhdGlvbiwgd2UgY29tcGFyZSB0aGUgcGVyZm9ybWFuY2Ugb2YgbW9kZWxzIHdpdGggZGlmZmVyZW50IHNwZWNpZmljYXRpb25zLgoKYGBge3IgbW9kZWxfc2V0dXB9CgojZ2JtIHBhcmFtZXRlcnMgdHVuaW5nOgpuLnRyZWVzID0gYygxMCw1MCwxMDAsMjAwKQpzaHJpbmthZ2UgPSBjKDAuMDEsMC4wNSwwLjEsMC4xNSkKCiNzdm0gcGFyYW1ldGVycyB0dW5pbmc6CmNvc3QgPSBjKDAuMDAwMDAwMSwwLjAwMDAwMSwwLjAwMDAxLDAuMDAwMSwwLjAwMSwwLjAxLDEpCm1vZGVsX2xhYmVsc19zdm0gPSBwYXN0ZSgiU1ZNIHdpdGggY29zdCA9IiwgY29zdCkKCiN4Z2Jvb3N0IHBhcmFtZXRlcnMgdHVuaW5nCnBhcmFtcyA8LSBsaXN0KGJvb3N0ZXIgPSAiZ2J0cmVlIiwgb2JqZWN0aXZlID0gImJpbmFyeTpsb2dpc3RpYyIsIAogICAgICAgICAgICAgICAgIGV0YT0wLjMsIGdhbW1hPTAsIG1heF9kZXB0aD02LCBtaW5fY2hpbGRfd2VpZ2h0PTEsIAogICAgICAgICAgICAgICAgIHN1YnNhbXBsZT0xLCBjb2xzYW1wbGVfYnl0cmVlPTEpCgojcmFuZG9tIGZvcmVzdCBwYXJhbWV0ZXJzIHR1bmluZwpudHJlZXMgPC0gMTI4IAojYWNjb3JkaW5nIHRvIGEgcGFwZXIgYnkgVGhhaXMgTWF5dW1pIE9zaGlybywgUGVkcm8gU2FudG9ybyBQZXJleiwgYW5kIEpvcyDMgWUgQXVndXN0byBCYXJhbmF1c2thLAojIHRoZSBBVUMgZ2FpbiBmb3IgaW5jcmVhc2luZyBudW1iZXIgb2YgdHJlZXMgaXMgbWluaW1hbCBhZnRlciAxMjgsIAojIGFmdGVyIG9ic2VydmluZyBhIG11bHRpdHVkZSBvZiB0aGVpciBkYXRhc2V0cwoKCmBgYAoKIyMjIFN0ZXAgMjogaW1wb3J0IGRhdGEgYW5kIHRyYWluLXRlc3Qgc3BsaXQgCmBgYHtyfQojdHJhaW4tdGVzdCBzcGxpdAppbmZvIDwtIHJlYWQuY3N2KHRyYWluX2xhYmVsX3BhdGgpCm4gPC0gbnJvdyhpbmZvKQpuX3RyYWluIDwtIHJvdW5kKG4qKDQvNSksIDApCnRyYWluX2lkeCA8LSBzYW1wbGUoaW5mbyRJbmRleCwgbl90cmFpbiwgcmVwbGFjZSA9IEYpCnRlc3RfaWR4IDwtIHNldGRpZmYoaW5mbyRJbmRleCwgdHJhaW5faWR4KQpgYGAKCklmIHlvdSBjaG9vc2UgdG8gZXh0cmFjdCBmZWF0dXJlcyBmcm9tIGltYWdlcywgc3VjaCBhcyB1c2luZyBHYWJvciBmaWx0ZXIsIFIgbWVtb3J5IHdpbGwgZXhoYXVzdCBhbGwgaW1hZ2VzIGFyZSByZWFkIHRvZ2V0aGVyLiBUaGUgc29sdXRpb24gaXMgdG8gcmVwZWF0IHJlYWRpbmcgYSBzbWFsbGVyIGJhdGNoKGUuZyAxMDApIGFuZCBwcm9jZXNzIHRoZW0uIApgYGB7cn0Kbl9maWxlcyA8LSBsZW5ndGgobGlzdC5maWxlcyh0cmFpbl9pbWFnZV9kaXIpKQoKaW1hZ2VfbGlzdCA8LSBsaXN0KCkKZm9yKGkgaW4gMToxMDApewogICBpbWFnZV9saXN0W1tpXV0gPC0gcmVhZEltYWdlKHBhc3RlMCh0cmFpbl9pbWFnZV9kaXIsIHNwcmludGYoIiUwNGQiLCBpKSwgIi5qcGciKSkKfQpgYGAKCkZpZHVjaWFsIHBvaW50cyBhcmUgc3RvcmVkIGluIG1hdGxhYiBmb3JtYXQuIEluIHRoaXMgc3RlcCwgd2UgcmVhZCB0aGVtIGFuZCBzdG9yZSB0aGVtIGluIGEgbGlzdC4KYGBge3IgcmVhZCBmaWR1Y2lhbCBwb2ludHN9CiNmdW5jdGlvbiB0byByZWFkIGZpZHVjaWFsIHBvaW50cwojaW5wdXQ6IGluZGV4CiNvdXRwdXQ6IG1hdHJpeCBvZiBmaWR1Y2lhbCBwb2ludHMgY29ycmVzcG9uZGluZyB0byB0aGUgaW5kZXgKcmVhZE1hdC5tYXRyaXggPC0gZnVuY3Rpb24oaW5kZXgpewogICAgIHJldHVybihyb3VuZChyZWFkTWF0KHBhc3RlMCh0cmFpbl9wdF9kaXIsIHNwcmludGYoIiUwNGQiLCBpbmRleCksICIubWF0IikpW1sxXV0sMCkpCn0KCiNsb2FkIGZpZHVjaWFsIHBvaW50cwpmaWR1Y2lhbF9wdF9saXN0IDwtIGxhcHBseSgxOm5fZmlsZXMsIHJlYWRNYXQubWF0cml4KQpzYXZlKGZpZHVjaWFsX3B0X2xpc3QsIGZpbGU9Ii4uL291dHB1dC9maWR1Y2lhbF9wdF9saXN0LlJEYXRhIikKYGBgCgojIyMgU3RlcCAzOiBjb25zdHJ1Y3QgZmVhdHVyZXMgYW5kIHJlc3BvbnNlcwoKKyBUaGUgZm9sbG93IHBsb3RzIHNob3cgaG93IHBhaXJ3aXNlIGRpc3RhbmNlIGJldHdlZW4gZmlkdWNpYWwgcG9pbnRzIGNhbiB3b3JrIGFzIGZlYXR1cmUgZm9yIGZhY2lhbCBlbW90aW9uIHJlY29nbml0aW9uLgoKICArIEluIHRoZSBmaXJzdCBjb2x1bW4sIDc4IGZpZHVjaWFscyBwb2ludHMgb2YgZWFjaCBlbW90aW9uIGFyZSBtYXJrZWQgaW4gb3JkZXIuIAogICsgSW4gdGhlIHNlY29uZCBjb2x1bW4gZGlzdHJpYnV0aW9ucyBvZiB2ZXJ0aWNhbCBkaXN0YW5jZSBiZXR3ZWVuIHJpZ2h0IHB1cGlsKDEpIGFuZCAgcmlnaHQgYnJvdyBwZWFrKDIxKSBhcmUgc2hvd24gaW4gIGhpc3RvZ3JhbXMuIEZvciBleGFtcGxlLCB0aGUgZGlzdGFuY2Ugb2YgYW4gYW5ncnkgZmFjZSB0ZW5kcyB0byBiZSBzaG9ydGVyIHRoYW4gdGhhdCBvZiBhIHN1cnByaXNlZCBmYWNlLgogICsgVGhlIHRoaXJkIGNvbHVtbiBpcyB0aGUgZGlzdHJpYnV0aW9ucyBvZiB2ZXJ0aWNhbCBkaXN0YW5jZXMgYmV0d2VlbiByaWdodCBtb3V0aCBjb3JuZXIoNTApCmFuZCB0aGUgbWlkcG9pbnQgb2YgdGhlIHVwcGVyIGxpcCg1MikuICBGb3IgZXhhbXBsZSwgdGhlIGRpc3RhbmNlIG9mIGFuIGhhcHB5IGZhY2UgdGVuZHMgdG8gYmUgc2hvcnRlciB0aGFuIHRoYXQgb2YgYSBzYWQgZmFjZS4KCiFbRmlndXJlMV0oLi4vZmlncy9mZWF0dXJlX3Zpc3VhbGl6YXRpb24uanBnKQoKYGZlYXR1cmUuUmAgc2hvdWxkIGJlIHRoZSB3cmFwcGVyIGZvciBhbGwgeW91ciBmZWF0dXJlIGVuZ2luZWVyaW5nIGZ1bmN0aW9ucyBhbmQgb3B0aW9ucy4gVGhlIGZ1bmN0aW9uIGBmZWF0dXJlKCApYCBzaG91bGQgaGF2ZSBvcHRpb25zIHRoYXQgY29ycmVzcG9uZCB0byBkaWZmZXJlbnQgc2NlbmFyaW9zIGZvciB5b3VyIHByb2plY3QgYW5kIHByb2R1Y2VzIGFuIFIgb2JqZWN0IHRoYXQgY29udGFpbnMgZmVhdHVyZXMgYW5kIHJlc3BvbnNlcyB0aGF0IGFyZSByZXF1aXJlZCBieSBhbGwgdGhlIG1vZGVscyB5b3UgYXJlIGdvaW5nIHRvIGV2YWx1YXRlIGxhdGVyLiAKICAKICArIGBmZWF0dXJlLlJgCiAgKyBJbnB1dDogbGlzdCBvZiBpbWFnZXMgb3IgZmlkdWNpYWwgcG9pbnQKICArIE91dHB1dDogYW4gUkRhdGEgZmlsZSB0aGF0IGNvbnRhaW5zIGV4dHJhY3RlZCBmZWF0dXJlcyBhbmQgY29ycmVzcG9uZGluZyByZXNwb25zZXMKCmBgYHtyIGZlYXR1cmV9CnNvdXJjZSgiLi4vbGliL2ZlYXR1cmUuUiIpCnRtX2ZlYXR1cmVfdHJhaW4gPC0gTkEKaWYocnVuLmZlYXR1cmUudHJhaW4pewogIHRtX2ZlYXR1cmVfdHJhaW4gPC0gc3lzdGVtLnRpbWUoZGF0X3RyYWluIDwtIGZlYXR1cmUoZmlkdWNpYWxfcHRfbGlzdCwgdHJhaW5faWR4KSkKICBzYXZlKGRhdF90cmFpbiwgZmlsZT0iLi4vb3V0cHV0L2ZlYXR1cmVfdHJhaW4uUkRhdGEiKQp9ZWxzZXsKICBsb2FkKGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3RyYWluLlJEYXRhIikKfQoKdG1fZmVhdHVyZV90ZXN0IDwtIE5BCmlmKHJ1bi5mZWF0dXJlLnRlc3QpewogIHRtX2ZlYXR1cmVfdGVzdCA8LSBzeXN0ZW0udGltZShkYXRfdGVzdCA8LSBmZWF0dXJlKGZpZHVjaWFsX3B0X2xpc3QsIHRlc3RfaWR4KSkKICBzYXZlKGRhdF90ZXN0LCBmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV90ZXN0LlJEYXRhIikKfWVsc2V7CiAgbG9hZChmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV90ZXN0LlJEYXRhIikKfQoKCmBgYAoKIyMjIFN0ZXAgNDogVHJhaW4gYSBjbGFzc2lmaWNhdGlvbiBtb2RlbCB3aXRoIHRyYWluaW5nIGZlYXR1cmVzIGFuZCByZXNwb25zZXMKQ2FsbCB0aGUgdHJhaW4gbW9kZWwgYW5kIHRlc3QgbW9kZWwgZnJvbSBsaWJyYXJ5LiAKCmB0cmFpbi5SYCBhbmQgYHRlc3QuUmAgc2hvdWxkIGJlIHdyYXBwZXJzIGZvciBhbGwgeW91ciBtb2RlbCB0cmFpbmluZyBzdGVwcyBhbmQgeW91ciBjbGFzc2lmaWNhdGlvbi9wcmVkaWN0aW9uIHN0ZXBzLiAKCisgYHRyYWluLlJgCiAgKyBJbnB1dDogYSBkYXRhIGZyYW1lIGNvbnRhaW5pbmcgZmVhdHVyZXMgYW5kIGxhYmVscyBhbmQgYSBwYXJhbWV0ZXIgbGlzdC4KICArIE91dHB1dDphIHRyYWluZWQgbW9kZWwKKyBgdGVzdC5SYAogICsgSW5wdXQ6IHRoZSBmaXR0ZWQgY2xhc3NpZmljYXRpb24gbW9kZWwgdXNpbmcgdHJhaW5pbmcgZGF0YSBhbmQgcHJvY2Vzc2VkIGZlYXR1cmVzIGZyb20gdGVzdGluZyBpbWFnZXMgCiAgKyBJbnB1dDogYW4gUiBvYmplY3QgdGhhdCBjb250YWlucyBhIHRyYWluZWQgY2xhc3NpZmllci4KICArIE91dHB1dDogdHJhaW5pbmcgbW9kZWwgc3BlY2lmaWNhdGlvbgoKCmBgYHtyIGxvYWRsaWJ9CnNvdXJjZSgiLi4vbGliL3RyYWluLlIiKSAKc291cmNlKCIuLi9saWIvdGVzdC5SIikKCnNvdXJjZSgiLi4vbGliL3RyYWluX2dibS5SIikKc291cmNlKCIuLi9saWIvdGVzdF9nYm0uUiIpCgpzb3VyY2UoIi4uL2xpYi90cmFpbl9TVk0uUiIpIApzb3VyY2UoIi4uL2xpYi90ZXN0X1NWTS5SIikKCnNvdXJjZSgiLi4vbGliL2ZpdF90cmFpbl94Z2Jvb3N0LlIiKQpzb3VyY2UoIi4uL2xpYi9maXRfdHJhaW5fcmFuZG9tZm9yZXN0LlIiKQpgYGAKCiMjIyMgTW9kZWwgc2VsZWN0aW9uIHdpdGggY3Jvc3MtdmFsaWRhdGlvbgoqIERvIG1vZGVsIHNlbGVjdGlvbiBieSBjaG9vc2luZyBhbW9uZyBkaWZmZXJlbnQgdmFsdWVzIG9mIHRyYWluaW5nIG1vZGVsIHBhcmFtZXRlcnMuCgoqKkJhc2VsaW5lIE1vZGVsKioKKyBCYXNlbGluZS9HQk0KYGBge3IgcnVuY3Z9CmZlYXR1cmVfdHJhaW4gPSBhcy5tYXRyaXgoZGF0X3RyYWluWywgLTYwMDddKQpsYWJlbF90cmFpbiA9IGFzLmludGVnZXIoZGF0X3RyYWluJGxhYmVsKSAKCnNvdXJjZSgiLi4vbGliL2Nyb3NzX3ZhbGlkYXRpb24uUiIpCnNvdXJjZSgiLi4vbGliL2Nyb3NzX3ZhbGlkYXRpb25fU1ZNLlIiKQpzb3VyY2UoIi4uL2xpYi9jdl9nYm0uUiIpCgppZihydW4uY3YuYmFzZWxpbmUpeyAgCiAgCiAgbWVhbl9lcnJvcl9jdiA8LSBtYXRyaXgoMCwgbnJvdyA9IGxlbmd0aChuLnRyZWVzKSwgbmNvbCA9IGxlbmd0aChzaHJpbmthZ2UpKQogIHNkX2Vycm9yX2N2IDwtIG1hdHJpeCgwLCBucm93ID0gbGVuZ3RoKG4udHJlZXMpLCBuY29sID0gbGVuZ3RoKHNocmlua2FnZSkpCiAgbWVhbl9hdWNfY3YgPC0gbWF0cml4KDAsIG5yb3cgPSBsZW5ndGgobi50cmVlcyksIG5jb2wgPSBsZW5ndGgoc2hyaW5rYWdlKSkKICBzZF9hdWNfY3YgPC0gbWF0cml4KDAsIG5yb3cgPSBsZW5ndGgobi50cmVlcyksIG5jb2wgPSBsZW5ndGgoc2hyaW5rYWdlKSkKCiAgZm9yKGkgaW4gMTpsZW5ndGgobi50cmVlcykpewogICAgY2F0KCJuLnRyZWVzID0iLCBuLnRyZWVzW2ldLCJcbiIpCiAgZm9yKGsgaW4gMTpsZW5ndGgoc2hyaW5rYWdlKSl7CiAgICBjYXQoInNocmlua2FnZSA9Iiwgc2hyaW5rYWdlW2tdLCJcbiIpCiAgICAKcmVzX2N2X2dibSA8LSBjdl9nYm0oZmVhdHVyZXMgPSBmZWF0dXJlX3RyYWluLCBsYWJlbHMgPSBsYWJlbF90cmFpbiwgSywgIG4udHJlZXMgPSBuLnRyZWVzW2ldLHNocmlua2FnZSA9IHNocmlua2FnZVtrXSxyZXdlaWdodCA9IHNhbXBsZS5yZXdlaWdodCkKICAgICAgCiAgICBtZWFuX2Vycm9yX2N2W2ksa108LXJlc19jdl9nYm1bMV0KICAgICBzZF9lcnJvcl9jdltpLGtdPC1yZXNfY3ZfZ2JtWzJdCiAgICAgIG1lYW5fYXVjX2N2W2ksa108LXJlc19jdl9nYm1bM10KICAgICAgIHNkX2F1Y19jdltpLGtdPC1yZXNfY3ZfZ2JtWzRdCiAgICAgIAogICAgc2F2ZShtZWFuX2Vycm9yX2N2LCBmaWxlPSIuLi9vdXRwdXQvbWVhbl9lcnJvcl9jdi5SRGF0YSIpCiAgICBzYXZlKHNkX2Vycm9yX2N2LCBmaWxlPSIuLi9vdXRwdXQvc2RfZXJyb3JfY3YuUkRhdGEiKQogICAgc2F2ZShtZWFuX2F1Y19jdiwgZmlsZT0iLi4vb3V0cHV0L21lYW5fYXVjX2N2LlJEYXRhIikKICAgIHNhdmUoc2RfYXVjX2N2LCBmaWxlPSIuLi9vdXRwdXQvc2RfYXVjX2N2LlJEYXRhIikgCiAgICB9fQogfSBlbHNlewogIGxvYWQoIi4uL291dHB1dC9tZWFuX2Vycm9yX2N2LlJEYXRhIikKICBsb2FkKCIuLi9vdXRwdXQvc2RfZXJyb3JfY3YuUkRhdGEiKSAgIAogIGxvYWQoIi4uL291dHB1dC9tZWFuX2F1Y19jdi5SRGF0YSIpCiAgbG9hZCgiLi4vb3V0cHV0L3NkX2F1Y19jdi5SRGF0YSIpCiAgICB9CgppZihydW4uY3YpewogIHJlc19jdiA8LSBtYXRyaXgoMCwgbnJvdyA9IGxlbmd0aChsbWJkKSwgbmNvbCA9IDQpCiAgZm9yKGkgaW4gMTpsZW5ndGgobG1iZCkpewogICAgY2F0KCJsYW1iZGEgPSAiLCBsbWJkW2ldLCAiXG4iKQogICAgcmVzX2N2W2ksXSA8LSBjdi5mdW5jdGlvbihmZWF0dXJlcyA9IGZlYXR1cmVfdHJhaW4sIGxhYmVscyA9IGxhYmVsX3RyYWluLCBLLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbCA9IGxtYmRbaV0sIHJld2VpZ2h0ID0gc2FtcGxlLnJld2VpZ2h0KQogIHNhdmUocmVzX2N2LCBmaWxlPSIuLi9vdXRwdXQvcmVzX2N2LlJEYXRhIikKICB9Cn1lbHNlewogIGxvYWQoIi4uL291dHB1dC9yZXNfY3YuUkRhdGEiKQp9CmBgYAoKYGBge3IgY3YgcmVzdWx0IGRmfQpsaWJyYXJ5KHRpZHlyKQoKZGZfbWVhbl9lcnJvcj1kYXRhLmZyYW1lKG1lYW5fZXJyb3JfY3YpJT4lCnNldE5hbWVzKHNocmlua2FnZSklPiUKbXV0YXRlKG4udHJlZXM9bi50cmVlcyklPiUKZ2F0aGVyKHNocmlua2FnZSxtZWFuX2Vycm9yLGAwLjAxYDpgMC4xNWApCgpkZl9zZF9lcnJvcj1kYXRhLmZyYW1lKHNkX2Vycm9yX2N2KSU+JQpzZXROYW1lcyhzaHJpbmthZ2UpJT4lCm11dGF0ZShuLnRyZWVzPW4udHJlZXMpJT4lCmdhdGhlcihzaHJpbmthZ2Usc2RfZXJyb3IsYDAuMDFgOmAwLjE1YCkKCmRmX21lYW5fYXVjPWRhdGEuZnJhbWUobWVhbl9hdWNfY3YpJT4lCnNldE5hbWVzKHNocmlua2FnZSklPiUKbXV0YXRlKG4udHJlZXM9bi50cmVlcyklPiUKZ2F0aGVyKHNocmlua2FnZSxtZWFuX2F1YyxgMC4wMWA6YDAuMTVgKQoKZGZfc2RfYXVjPWRhdGEuZnJhbWUoc2RfYXVjX2N2KSU+JQpzZXROYW1lcyhzaHJpbmthZ2UpJT4lCm11dGF0ZShuLnRyZWVzPW4udHJlZXMpJT4lCmdhdGhlcihzaHJpbmthZ2Usc2RfYXVjLGAwLjAxYDpgMC4xNWApCgpyZXNfY3ZfZ2JtIDwtIGRmX21lYW5fZXJyb3IlPiVtdXRhdGUoc2RfZXJyb3I9ZGZfc2RfZXJyb3Ikc2RfZXJyb3IsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZWFuX2F1Yz1kZl9tZWFuX2F1YyRtZWFuX2F1YywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNkX2F1Yz1kZl9zZF9hdWMkc2RfYXVjKQpzYXZlKHJlc19jdl9nYm0sZmlsZSA9ICIuLi9vdXRwdXQvcmVzX2N2X2dibS5SRGF0YSIpCmBgYAoKKyBTVk0KYGBge3Igc3ZtfQppZihydW4uY3Yuc3ZtKXsKICByZXNfY3Zfc3ZtIDwtIG1hdHJpeCgwLCBucm93ID0gbGVuZ3RoKGNvc3QpLCBuY29sID0gNCkKICBmb3IoaSBpbiAxOmxlbmd0aChjb3N0KSl7CiAgICBjYXQoImNvc3Q9ICIsIGNvc3RbaV0sICJcbiIpCiAgICByZXNfY3Zfc3ZtW2ksXSA8LSBzdm1fY3YoZmVhdHVyZXMgPSBmZWF0dXJlX3RyYWluLCBsYWJlbHMgPSBsYWJlbF90cmFpbiwgSywgY29zdD1jb3N0W2ldLCByZXdlaWdodCA9IHNhbXBsZS5yZXdlaWdodCkKICBzYXZlKHJlc19jdl9zdm0sIGZpbGU9Ii4uL291dHB1dC9yZXNfY3Zfc3ZtLlJEYXRhIikKICB9Cn1lbHNlewogIGxvYWQoIi4uL291dHB1dC9yZXNfY3Zfc3ZtLlJEYXRhIikKfQpgYGAKCioqSW1wcm92ZWQgTW9kZWwqKgorIFhHQm9vc3QgCmBgYHtyIFBhcmFtZXRlciBPcHRpbWl6YXRpb24gVGhyb3VnaCBDcm9zcyBWYWxpZGF0aW9uIGZvciBYR0Jvb3N0fQoKc291cmNlKCIuLi9saWIveGdib29zdF9jdi5SIikKZmVhdHVyZV90cmFpbiA9IGFzLm1hdHJpeChkYXRfdHJhaW5bLCAtNjAwN10pCmxhYmVsX3RyYWluID0gYXMuaW50ZWdlcihkYXRfdHJhaW4kbGFiZWwpIApsYWJlbF90cmFpbl94Z2IgPC0gbGFiZWxfdHJhaW4KbGFiZWxfdHJhaW5feGdiW2xhYmVsX3RyYWluX3hnYiA9PSAyXSA8LSAwCgpzZXRfcm91bmRzICA8LSA1MApLIDwtIDUKCm5ld19wYXJhbXMgPC0gY3ZfeGdib29zdChwYXJhbXMsIGZlYXR1cmVfdHJhaW4sIGxhYmVsX3RyYWluX3hnYiwgc2V0X3JvdW5kcywgSykKCnNhdmUobmV3X3BhcmFtcywgZmlsZT0iLi4vb3V0cHV0L3Jlc19jdl94Z2Jvb3N0LlJEYXRhIikKCmBgYCAKClZpc3VhbGl6ZSBjcm9zcy12YWxpZGF0aW9uIHJlc3VsdHMuIApgYGB7ciBjdl92aXN9CmxvYWQoIi4uL291dHB1dC9yZXNfY3ZfZ2JtLlJEYXRhIikKCgppZihydW4uY3YuYmFzZWxpbmUpewogIHAxIDwtIHJlc19jdl9nYm0gJT4lIAogICAgZ2dwbG90KGFlcyh4ID0gbi50cmVlcywgeSA9IG1lYW5fZXJyb3IsCiAgICAgICAgICAgICAgIHltaW4gPSBtZWFuX2Vycm9yIC0gc2RfZXJyb3IsIHltYXggPSBtZWFuX2Vycm9yICArc2RfZXJyb3IpKSArIAogICAgZ2VvbV9jcm9zc2JhcigpICsKICAgIGZhY2V0X3dyYXAofnNocmlua2FnZSkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkKICAKICBwMiA8LSByZXNfY3ZfZ2JtICU+JSAKICAgIGdncGxvdChhZXMoeCA9IG4udHJlZXMsIHkgPSBtZWFuX2F1YywKICAgICAgICAgICAgICAgeW1pbiA9IG1lYW5fYXVjIC0gc2RfYXVjLCB5bWF4ID0gbWVhbl9hdWMgKyBzZF9hdWMpKSArICAgICBmYWNldF93cmFwKH5zaHJpbmthZ2UpICsKICAgIGdlb21fY3Jvc3NiYXIoKSArCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKQogIAogIHByaW50KHAxKQogIHByaW50KHAyKQp9CgpyZXNfY3Zfc3ZtIDwtIGFzLmRhdGEuZnJhbWUocmVzX2N2X3N2bSkgCmNvbG5hbWVzKHJlc19jdl9zdm0pIDwtIGMoIm1lYW5fZXJyb3IiLCAic2RfZXJyb3IiLCAibWVhbl9BVUMiLCAic2RfQVVDIikKcmVzX2N2X3N2bSRrID0gYXMuZmFjdG9yKGNvc3QpCnJ1bi5jdi5zdm0gPC0gVFJVRQppZihydW4uY3Yuc3ZtKXsKICBwMSA8LSByZXNfY3Zfc3ZtICU+JSAKICAgIGdncGxvdChhZXMoeCA9IGFzLmZhY3Rvcihjb3N0KSwgeSA9IG1lYW5fZXJyb3IsCiAgICAgICAgICAgICAgIHltaW4gPSBtZWFuX2Vycm9yIC0gc2RfZXJyb3IsIHltYXggPSBtZWFuX2Vycm9yICsgc2RfZXJyb3IpKSArIAogICAgZ2VvbV9jcm9zc2JhcigpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpCiAgCiAgcDIgPC0gcmVzX2N2X3N2bSAlPiUgCiAgICBnZ3Bsb3QoYWVzKHggPSBhcy5mYWN0b3IoY29zdCksIHkgPSBtZWFuX0FVQywKICAgICAgICAgICAgICAgeW1pbiA9IG1lYW5fQVVDIC0gc2RfQVVDLCB5bWF4ID0gbWVhbl9BVUMgKyBzZF9BVUMpKSArIAogICAgZ2VvbV9jcm9zc2JhcigpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpCiAgCiAgcHJpbnQocDEpCiAgcHJpbnQocDIpCn0KYGBgCgoKKiBDaG9vc2UgdGhlICJiZXN0IiBwYXJhbWV0ZXIgdmFsdWUgZm9yIGJhc2VsaW5lIG1vZGVsCmBgYHtyIGJlc3RfbW9kZWx9CmJlc3Rfbi50cmVlcyA9IGFzLm51bWVyaWMocmVzX2N2X2dibVt3aGljaC5taW4ocmVzX2N2X2dibSRtZWFuX2Vycm9yKSwxXSkKYmVzdF9zaHJpbmthZ2UgPSBhcy5udW1lcmljKHJlc19jdl9nYm1bd2hpY2gubWluKHJlc19jdl9nYm0kbWVhbl9lcnJvciksMl0pCgpiZXN0X2Nvc3QgPC0gY29zdFt3aGljaC5taW4ocmVzX2N2X3N2bSRtZWFuX2Vycm9yKV0KYGBgCgoqIENob29zZSAiYmVzdCIgbnVtYmVyIG9mIHRyZWVzIGFuZCBtdHJ5IGZvciByYW5kb20gZm9yZXN0IG1vZGVsCmBgYHtyIGJlc3RfbW9kZWxfcmZ9CnNvdXJjZSgiLi4vbGliL3JmX3BhcmFtX2Nob2ljZS5SIikKZmVhdHVyZV90cmFpbiA9IGFzLm1hdHJpeChkYXRfdHJhaW5bLCAtNjAwN10pCmxhYmVsX3RyYWluID0gYXMuaW50ZWdlcihkYXRfdHJhaW4kbGFiZWwpIAppbml0X210cnkgPC0gc3FydChuY29sKGRhdF90cmFpbikpCgpyZl9vcHRfdHJlZSA8LSBwYXJhbV9jaG9pY2VfcmYoZmVhdHVyZV90cmFpbiA9IGZlYXR1cmVfdHJhaW4sIGxhYmVsX3RyYWluID0gIGxhYmVsX3RyYWluLCBtdHJ5ID0gaW5pdF9tdHJ5LCBudHJlZSA9IG50cmVlcykKCnNhdmUocmZfb3B0X3RyZWUsIGZpbGUgPSAiLi4vb3V0cHV0L3Jlc19vb2JfcmYuUkRhdGEiKQpgYGAKCgojIyMjIFRyYWluIGRpZmZlcmVudCBtb2RlbHMKKyBUcmFpbiB0aGUgYmFzZWxpbmUgbW9kZWwgd2l0aCB0aGUgZW50aXJlIHRyYWluaW5nIHNldCB1c2luZyB0aGUgc2VsZWN0ZWQgbW9kZWwgKG1vZGVsIHBhcmFtZXRlcikgdmlhIGNyb3NzLXZhbGlkYXRpb24uCmBgYHtyIGZpbmFsX3RyYWluX2Jhc2VsaW5lfQojIHRyYWluaW5nIHdlaWdodHMKd2VpZ2h0X3RyYWluIDwtIHJlcChOQSwgbGVuZ3RoKGxhYmVsX3RyYWluKSkKZm9yICh2IGluIHVuaXF1ZShsYWJlbF90cmFpbikpewogIHdlaWdodF90cmFpbltsYWJlbF90cmFpbiA9PSB2XSA9IDAuNSAqIGxlbmd0aChsYWJlbF90cmFpbikgLyBsZW5ndGgobGFiZWxfdHJhaW5bbGFiZWxfdHJhaW4gPT0gdl0pCn0KaWYgKHNhbXBsZS5yZXdlaWdodCl7CiAgdG1fdHJhaW5fYmFzZWxpbmUgPC0gc3lzdGVtLnRpbWUoZml0X3RyYWluX2Jhc2VsaW5lIDwtIHRyYWluX2dibShmZWF0dXJlX3RyYWluLCBsYWJlbF90cmFpbiwgdyA9IHdlaWdodF90cmFpbixiZXN0X24udHJlZXMsIGJlc3Rfc2hyaW5rYWdlKSkKfSBlbHNlIHsKICB0bV90cmFpbl9iYXNlbGluZSA8LSBzeXN0ZW0udGltZShmaXRfdHJhaW5fYmFzZWxpbmUgPC0gdHJhaW5fZ2JtKGZlYXR1cmVfdHJhaW4sIGxhYmVsX3RyYWluLCB3ID0gTlVMTCwgYmVzdF9uLnRyZWVzLCBiZXN0X3Nocmlua2FnZSkpCn0Kc2F2ZShmaXRfdHJhaW5fYmFzZWxpbmUsIGZpbGU9Ii4uL291dHB1dC9maXRfdHJhaW5fYmFzZWxpbmUuUkRhdGEiKQpgYGAKCisgVHJhaW4gdGhlIFNWTSBtb2RlbCB3aXRoIHRoZSBlbnRpcmUgdHJhaW5pbmcgc2V0IHVzaW5nIHRoZSBzZWxlY3RlZCBtb2RlbCAobW9kZWwgcGFyYW1ldGVyKSB2aWEgY3Jvc3MtdmFsaWRhdGlvbi4KYGBge3IgZmluYWxfdHJhaW5fc3ZtfQp3ZWlnaHRfdHJhaW4gPC0gcmVwKE5BLCBsZW5ndGgobGFiZWxfdHJhaW4pKQpmb3IgKHYgaW4gdW5pcXVlKGxhYmVsX3RyYWluKSl7CiAgd2VpZ2h0X3RyYWluW2xhYmVsX3RyYWluID09IHZdID0gMC41ICogbGVuZ3RoKGxhYmVsX3RyYWluKSAvIGxlbmd0aChsYWJlbF90cmFpbltsYWJlbF90cmFpbiA9PSB2XSkKfQppZiAoc2FtcGxlLnJld2VpZ2h0KXsKICB0bV90cmFpbl9zdm0gPC0gc3lzdGVtLnRpbWUoZml0X3RyYWluX3N2bSA8LXN2bV90cmFpbihmZWF0dXJlX3RyYWluLCBsYWJlbF90cmFpbiwgdyA9IHdlaWdodF90cmFpbiwgYmVzdF9jb3N0KSkKfSBlbHNlIHsKICB0bV90cmFpbl9zdm0gPC0gc3lzdGVtLnRpbWUoZml0X3RyYWluX3N2bSA8LXN2bV90cmFpbihmZWF0dXJlX3RyYWluLCBsYWJlbF90cmFpbiwgdyA9IE5VTEwsIGJlc3RfY29zdCkpCn0Kc2F2ZShmaXRfdHJhaW5fc3ZtLCBmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX3N2bS5SRGF0YSIpCmBgYAoKCisgVHJhaW4gdGhlIFhHQm9vc3QgbW9kZWwgd2l0aCBvcHRpbWFsIHBhcmFtZXRlcnMKCmBgYHtyIGZpbmFsX3RyYWluX3hnYn0KCnhnYl90cmFpbl90aW1lIDwtIHN5c3RlbS50aW1lKGZpdF90cmFpbl94Z2IgPC0geGdib29zdF90cmFpbihmZWF0dXJlcyA9IGZlYXR1cmVfdHJhaW4sIGxhYmVscyA9IGxhYmVsX3RyYWluX3hnYiwgcGFyYW1zID0gbmV3X3BhcmFtcywgcm91bmRzID0gIHNldF9yb3VuZHMpKQoKc2F2ZShmaXRfdHJhaW5feGdiLCBmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX3hnYi5SRGF0YSIpCgpgYGAKCisgVHJhaW4gdGhlIFJhbmRvbSBGb3Jlc3QgbW9kZWwgd2l0aCBvcHRpbWFsIHBhcmFtZXRlcnMKCmBgYHtyIGZpbmFsX3RyYWluX3JmfQpsb2FkKGZpbGUgPSAiLi4vb3V0cHV0L3Jlc19vb2JfcmYuUkRhdGEiKQpyZl90cmFpbl90aW1lIDwtIHN5c3RlbS50aW1lKGZpdF90cmFpbl9yYW5kb21mb3Jlc3QgPC0gcmZfdHJhaW4oZmVhdHVyZV90cmFpbiA9IGZlYXR1cmVfdHJhaW4sIGxhYmVsX3RyYWluID0gbGFiZWxfdHJhaW4sIG10cnkgPSByZl9vcHRfdHJlZSRtdHJ5LCBudHJlZSA9IHJmX29wdF90cmVlJG50cmVlKSkKc2F2ZShmaXRfdHJhaW5fcmFuZG9tZm9yZXN0LCBmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX3JmLlJEYXRhIikKYGBgCgorIHRyYWluIFBDQSArIExEQQpgYGB7ciBQQ0F9CgojUENBIGZvciB0cmFpbmluZyBmZWF0dXJlcwpzb3VyY2UoIi4uL2xpYi90cmFpbl9QQ0EuUiIpCgojIG1ha2UgZGF0X3RyYWluIGEgbnVtZXJpYyBkYXRhIGZyYW1lCmRhdF90cmFpbi5uZXcgPC0gbWF0cml4KDAsIG5jb2wgPSBuY29sKGRhdF90cmFpbikgLSAxLCBucm93ID0gbnJvdyhkYXRfdHJhaW4pKQpmb3IgKGkgaW4gMToobmNvbChkYXRfdHJhaW4pIC0gMSkpIHsKZGF0X3RyYWluLm5ld1ssaV0gPC0gYXMubnVtZXJpYyhkYXRfdHJhaW5bW2ldXSkKfQpkYXRfdHJhaW4ubmV3IDwtIGFzLmRhdGEuZnJhbWUoZGF0X3RyYWluLm5ldykKCiNQQ0EgZm9yIHRyYWluaW5nIGZlYXR1cmVzCnRtX3RyYWluX3BjYSA8LSBzeXN0ZW0udGltZShmaXRfdHJhaW5fcGNhIDwtIHRyYWluX3BjYShkYXRfdHJhaW4ubmV3KSkKCnNhdmUoZml0X3RyYWluX3BjYSwgZmlsZT0iLi4vb3V0cHV0L3BjYV90cmFpbi5SRGF0YSIpCgojIGRldGVybWluZSB0aGUgaW1wb3J0YW50IHByaW5jaXBsZSBjb21wb25lbnRzCnNjcmVlcGxvdChmaXRfdHJhaW5fcGNhLCB0eXBlID0gImwiKQoKIyBUaGUgcHJvcG9ydGlvbiBvZiB2YXJpYW5jZSBmb3IgZmlyc3QgMzAwIFBDcwpzdW0oKGZpdF90cmFpbl9wY2Ekc2RldlsxOjMwMF0pXjIpIC8gc3VtKChmaXRfdHJhaW5fcGNhJHNkZXYpXjIpCgpgYGAKVGhlcmVmb3JlLCB3ZSBjYW4gY2hvb3NlIHRoZSBmaXJzdCAzMDAgcHJpbmNpcGxlIGNvbXBvbmVudHMgYmVjYXVzZSB0aGV5IGV4cGxhaW4gbW9zdCBvZiB0aGUgdG90YWwgdmFyaWFuY2UsIHdoaWNoIGlzIGFyb3VuZCA5OS45JS4KCmBgYHtyIFBDQSB0cmFpbn0KIyBnZXQgdGhlIGZlYXR1cmVzIG9mIHByaW5jaXBsZSBjb21wb25lbnRzIHdpdGggZW1vdGlvbiBpbmRleApkYXRfdHJhaW5fcGNhIDwtIGRhdGEuZnJhbWUoZml0X3RyYWluX3BjYSR4WywxOjMwMF0sIGVtb3Rpb25faWR4ID0gZGF0X3RyYWluWyw2MDA3XSkKYGBgCgpVc2UgdHJhaW5lZCBQQ0EgbW9kZWwgdG8gdGVzdCBkYXRhCmBgYHtyIFBDQSB0ZXN0fQpzb3VyY2UoIi4uL2xpYi90ZXN0X1BDQS5SIikKZGF0X3Rlc3QubmV3IDwtIGRhdF90ZXN0CmNvbG5hbWVzKGRhdF90ZXN0Lm5ldykgPC0gYyhjb2xuYW1lcyhkYXRfdHJhaW4ubmV3KSwgImVtb3Rpb25faWR4IikKCnRtX3Rlc3RfcGNhIDwtIHN5c3RlbS50aW1lKGRhdF90ZXN0Lm5ldyA8LSB0ZXN0X3BjYShmaXRfdHJhaW5fcGNhLCBkYXRfdGVzdC5uZXcpKQoKI2ZlYXR1cmVzIG9mIHRlc3RpbmcgcHJpbmNpcGxlIGNvbXBvbmVudHMgd2l0aCB0aGUgZW1vdGlvbiBpbmRleApkYXRfdGVzdF9wY2EgPC0gZGF0YS5mcmFtZShkYXRfdGVzdC5uZXdbLDE6MzAwXSwgZW1vdGlvbl9pZHggPSBkYXRfdGVzdFssNjAwN10pCgpzYXZlKGRhdF90cmFpbl9wY2EsIGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3RyYWluX3BjYS5SRGF0YSIpCnNhdmUoZGF0X3Rlc3RfcGNhLCBmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV90ZXN0X3BjYS5SRGF0YSIpCmBgYAoKQXBwbHkgTERBIG1vZGVsCgpgYGB7ciBMREEgdHJhaW59CiNsb2FkKCIuLi9vdXRwdXQvZmVhdHVyZV90cmFpbl9wY2EuUkRhdGEiKQoKI3RyYWluIExEQSBtb2RlbApzb3VyY2UoIi4uL2xpYi90cmFpbl9MREEuUiIpCnRtX3RyYWluX2xkYSA8LSBzeXN0ZW0udGltZShmaXRfdHJhaW5fbGRhIDwtIHRyYWluX2xkYShkYXRfdHJhaW5fcGNhKSkKc2F2ZShmaXRfdHJhaW5fbGRhLCBmaWxlPSIuLi9vdXRwdXQvTERBX3RyYWluLlJEYXRhIikKCiN0cmFpbmluZyBhY2N1cmFjeSBpbiBMREEgbW9kZWwKc291cmNlKCIuLi9saWIvdGVzdF9MREEuUiIpCnByZWRfdHJhaW5fbGRhIDwtIHRlc3RfbGRhKGZpdF90cmFpbl9sZGEsIGRhdF90cmFpbl9wY2EpCmFjY3VfdHJhaW5fbGRhIDwtIG1lYW4ocHJlZF90cmFpbl9sZGEgPT0gZGF0X3RyYWluX3BjYSRlbW90aW9uX2lkeCkKCmNhdCgiVGhlIHRyYWluaWcgYWNjdXJhY3kgb2YgbW9kZWwgTERBIiwgImlzIiwgYWNjdV90cmFpbl9sZGEqMTAwLCAiJS5cbiIpCmNhdCgiVGltZSBmb3IgdHJhaW5pbmcgbW9kZWwgTERBID0gIiwgdG1fdHJhaW5fbGRhWzFdLCAicyBcbiIpCmBgYAoKCiMjIyBTdGVwIDU6IFJ1biB0ZXN0IG9uIHRlc3QgaW1hZ2VzCgorQmFzZWxpbmUgbW9kZWwKYGBge3IgdGVzdCBnYm19CnRtX3Rlc3RfYmFzZWxpbmUgPSBOQQpmZWF0dXJlX3Rlc3QgPC0gYXMubWF0cml4KGRhdF90ZXN0WywgLTYwMDddKQppZihydW4udGVzdCl7CiAgbG9hZChmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX2Jhc2VsaW5lLlJEYXRhIikKICB0bV90ZXN0X2Jhc2VsaW5lIDwtIHN5c3RlbS50aW1lKHtsYWJlbF9wcmVkX2Jhc2VsaW5lIDwtIGFzLmludGVnZXIodGVzdF9nYm0oZml0X3RyYWluX2Jhc2VsaW5lLGZlYXR1cmVfdGVzdCxiZXN0X24udHJlZXMsIGJlc3Rfc2hyaW5rYWdlLCBwcmVkLnR5cGUgPSAnbGluaycpKTsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvYl9wcmVkX2Jhc2VsaW5lIDwtIHRlc3RfZ2JtKGZpdF90cmFpbl9iYXNlbGluZSwgZmVhdHVyZV90ZXN0LGJlc3Rfbi50cmVlcywgYmVzdF9zaHJpbmthZ2UsIHByZWQudHlwZSA9ICdyZXNwb25zZScpfSkKfQpgYGAKCitTVk0gbW9kZWwKYGBge3IgdGVzdCBzdm19CnRtX3Rlc3QgPSBOQQpmZWF0dXJlX3Rlc3QgPC0gYXMubWF0cml4KGRhdF90ZXN0WywgLTYwMDddKQppZihydW4udGVzdC5zdm0pewogIGxvYWQoZmlsZT0iLi4vb3V0cHV0L2ZpdF90cmFpbl9zdm0uUkRhdGEiKQogIHRtX3Rlc3Rfc3ZtIDwtIHN5c3RlbS50aW1lKHsKICAgIGxhYmVsX3ByZWRfc3ZtIDwtIGFzLmludGVnZXIoc3ZtX3Rlc3QoZml0X3RyYWluX3N2bSwgZmVhdHVyZV90ZXN0LCBwcmVkLnR5cGUgPSAnY2xhc3MnKSk7IAogICAgcHJvYl9wcmVkX3N2bSA8LSBzdm1fdGVzdChmaXRfdHJhaW5fc3ZtLCBmZWF0dXJlX3Rlc3QsIHByZWQudHlwZSA9ICdyZXNwb25zZScpfSkKfQpgYGAKCitYR0Jvb3N0CmBgYHtyIHRlc3QgeGdib29zdH0KdG1fdGVzdF94Z2IgPSBOQQpmZWF0dXJlX3Rlc3QgPC0gYXMubWF0cml4KGRhdF90ZXN0WywgLTYwMDddKQppZihydW4udGVzdCl7CiAgbG9hZChmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX3hnYi5SRGF0YSIpCiAgdG1fdGVzdF94Z2IgPC0gc3lzdGVtLnRpbWUoe2xhYmVsX3ByZWRfeGdiIDwtIHByZWRpY3QoZml0X3RyYWluX3hnYiwgZmVhdHVyZV90ZXN0LCBwcmVkLnR5cGUgPSAnY2xhc3MnKTsKICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbF9wcmVkX3hnYltsYWJlbF9wcmVkX3hnYiA+PSAwLjVdIDwtIDE7CiAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfcHJlZF94Z2JbbGFiZWxfcHJlZF94Z2IgPCAwLjVdIDwtICAwOwogICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JfcHJlZF94Z2IgPC0gcHJlZGljdChmaXRfdHJhaW5feGdiLCBmZWF0dXJlX3Rlc3QsIHByZWQudHlwZSA9ICdyZXNwb25zZScpfSkKfQpgYGAKCitSYW5kb20gRm9yZXN0CmBgYHtyIHRlc3QgcmFuZG9tIGZvcmVzdCB9CnRtX3Rlc3RfcmYgPSBOQQpmZWF0dXJlX3Rlc3QgPC0gYXMubWF0cml4KGRhdF90ZXN0WywgLTYwMDddKQppZihydW4udGVzdCl7CiAgbG9hZChmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX3JmLlJEYXRhIikKICB0bV90ZXN0X3JmIDwtIHN5c3RlbS50aW1lKHtsYWJlbF9wcmVkX3JmIDwtIHByZWRpY3QoZml0X3RyYWluX3JhbmRvbWZvcmVzdCwgZmVhdHVyZV90ZXN0LCBwcmVkLnR5cGUgPSAnY2xhc3MnKTsKICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9iX3ByZWRfcmYgPC0gcHJlZGljdChmaXRfdHJhaW5fcmFuZG9tZm9yZXN0LCBmZWF0dXJlX3Rlc3QsIHR5cGUgPSAncHJvYicpfSkKfSAKCmBgYAoKK0xEQQpgYGB7cn0Kc291cmNlKCIuLi9saWIvdGVzdF9MREEuUiIpCmxvYWQoZmlsZSA9ICIuLi9vdXRwdXQvZmVhdHVyZV90ZXN0X3BjYS5SRGF0YSIpCmxvYWQoZmlsZT0iLi4vb3V0cHV0L0xEQV90cmFpbi5SRGF0YSIpCnRtX3Rlc3RfbGRhIDwtIHN5c3RlbS50aW1lKHByZWRfbGRhIDwtIHRlc3RfbGRhKGZpdF90cmFpbl9sZGEsIGRhdF90ZXN0X3BjYSkpCgpgYGAKCgojIyMgRXZhbHVhdGlvbgoqQmFzZWxpbmUgTW9kZWwKYGBge3IgYmFzZWxpbmV9CiMjIHJld2VpZ2h0IHRoZSB0ZXN0IGRhdGEgdG8gcmVwcmVzZW50IGEgYmFsYW5jZWQgbGFiZWwgZGlzdHJpYnV0aW9uCmxhYmVsX3Rlc3QgPC0gYXMuaW50ZWdlcihkYXRfdGVzdCRsYWJlbCkKd2VpZ2h0X3Rlc3QgPC0gcmVwKE5BLCBsZW5ndGgobGFiZWxfdGVzdCkpCmZvciAodiBpbiB1bmlxdWUobGFiZWxfdGVzdCkpewogIHdlaWdodF90ZXN0W2xhYmVsX3Rlc3QgPT0gdl0gPSAwLjUgKiBsZW5ndGgobGFiZWxfdGVzdCkgLyBsZW5ndGgobGFiZWxfdGVzdFtsYWJlbF90ZXN0ID09IHZdKQp9CgphY2N1X2Jhc2VsaW5lIDwtIG1lYW4obGFiZWxfcHJlZF9iYXNlbGluZSA9PSBsYWJlbF90ZXN0KQp0cHIuZnByLmJhc2VsaW5lIDwtIFdlaWdodGVkUk9DKHByb2JfcHJlZF9iYXNlbGluZSwgbGFiZWxfdGVzdCwgd2VpZ2h0X3Rlc3QpCmF1Y19iYXNlbGluZSA8LSBXZWlnaHRlZEFVQyh0cHIuZnByLmJhc2VsaW5lKQoKCmNhdCgiVGhlIGFjY3VyYWN5IG9mIG1vZGVsIEdCTTogd2l0aCBuLnRyZWVzPSIsYmVzdF9uLnRyZWVzLCJhbmQgc2hyaW5rYWdlID0iLCBiZXN0X3Nocmlua2FnZSwgImlzIiwgYWNjdV9iYXNlbGluZSoxMDAsICIlLlxuIikKY2F0KCJUaGUgQVVDIG9mIG1vZGVsIEdCTTogd2l0aCBuLnRyZWVzPSIsIGJlc3Rfbi50cmVlcywiYW5kIHNocmlua2FnZSA9IiwgYmVzdF9zaHJpbmthZ2UsICJpcyIsIGF1Y19iYXNlbGluZSwgIi5cbiIpCgoKYGBgCgoKKlNWTQpgYGB7ciBldmFsdXRhdGlvbl9TVk19CmxhYmVsX3Rlc3QgPC0gYXMuaW50ZWdlcihkYXRfdGVzdCRsYWJlbCkKd2VpZ2h0X3Rlc3QgPC0gcmVwKE5BLCBsZW5ndGgobGFiZWxfdGVzdCkpCmZvciAodiBpbiB1bmlxdWUobGFiZWxfdGVzdCkpewogIHdlaWdodF90ZXN0W2xhYmVsX3Rlc3QgPT0gdl0gPSAwLjUgKiBsZW5ndGgobGFiZWxfdGVzdCkgLyBsZW5ndGgobGFiZWxfdGVzdFtsYWJlbF90ZXN0ID09IHZdKQp9CgphY2N1X3N2bSA8LSBtZWFuKGxhYmVsX3ByZWRfc3ZtID09IGxhYmVsX3Rlc3QpCnRwci5mcHIuc3ZtIDwtIFdlaWdodGVkUk9DKHByb2JfcHJlZF9zdm0sIGxhYmVsX3Rlc3QsIHdlaWdodF90ZXN0KQphdWNfc3ZtIDwtIFdlaWdodGVkQVVDKHRwci5mcHIuc3ZtKQpjYXQoIlRoZSBhY2N1cmFjeSBvZiBtb2RlbDoiLCBtb2RlbF9sYWJlbHNfc3ZtW3doaWNoLm1pbihyZXNfY3Zfc3ZtJG1lYW5fZXJyb3IpXSwgImlzIiwgYWNjdV9zdm0qMTAwLCAiJS5cbiIpCmNhdCgiVGhlIEFVQyBvZiBtb2RlbDoiLCBtb2RlbF9sYWJlbHNfc3ZtW3doaWNoLm1pbihyZXNfY3Zfc3ZtJG1lYW5fZXJyb3IpXSwgImlzIiwgYXVjX3N2bSwgIi5cbiIpCgoKYGBgCgoqWEdCb29zdApgYGB7ciBldmFsdWF0aW9uX1hHQn0KbGFiZWxfdGVzdCA8LSBhcy5pbnRlZ2VyKGRhdF90ZXN0JGxhYmVsKQpsYWJlbF90ZXN0X3hnYiA8LSBsYWJlbF90ZXN0CmxhYmVsX3Rlc3RfeGdiW2xhYmVsX3Rlc3RfeGdiPT0yXSA9IDAKYWNjdV94Z2IgPC0gbWVhbigobGFiZWxfcHJlZF94Z2IgPT0gbGFiZWxfdGVzdF94Z2IpKQp0cHIuZnByX3hnYiA8LSBXZWlnaHRlZFJPQyhwcm9iX3ByZWRfeGdiLCBsYWJlbF90ZXN0X3hnYikKYXVjX3hnYiA8LSBXZWlnaHRlZEFVQyh0cHIuZnByX3hnYikKCgpjYXQoIlRoZSBhY2N1cmFjeSBvZiB0aGUgWEdCb29zdCBtb2RlbDoiLCAiaXMiLCBhY2N1X3hnYioxMDAsICIlLlxuIikKY2F0KCJUaGUgQVVDIG9mIHRoZSBYR0Jvb3N0IG1vZGVsOiIsICJpcyIsIGF1Y194Z2IsICIuXG4iKQoKYGBgCgoqUmFuZG9tIEZvcmVzdApgYGB7ciBldmFsdWF0aW9uX3JmfQpsYWJlbF90ZXN0IDwtIGFzLmludGVnZXIoZGF0X3Rlc3QkbGFiZWwpCmFjY3VfcmYgPC0gbWVhbihsYWJlbF9wcmVkX3JmID09IGxhYmVsX3Rlc3QpCnRwci5mcHIucmYgPC0gV2VpZ2h0ZWRST0MocHJvYl9wcmVkX3JmWywyXSwgbGFiZWxfdGVzdCkKYXVjX3JmIDwtIFdlaWdodGVkQVVDKHRwci5mcHIucmYpCgoKY2F0KCJUaGUgYWNjdXJhY3kgb2YgdGhlIFJhbmRvbSBGb3Jlc3QgbW9kZWw6IiwgImlzIiwgYWNjdV9yZioxMDAsICIlLlxuIikKY2F0KCJUaGUgQVVDIG9mIHRoZSBSYW5kb20gRm9yZXN0IG1vZGVsOiIsICJpcyIsIGF1Y19yZiwgIi5cbiIpCmBgYAoKKkxEQQpgYGB7ciBldmFsdWF0aW9uX1BDQStMREF9CmFjY3VfbGRhIDwtIG1lYW4oZGF0X3Rlc3RfcGNhJGVtb3Rpb25faWR4ID09IHByZWRfbGRhKQpsYWJlbF9sZGEgPC0gYXMubnVtZXJpYyhkYXRfdGVzdF9wY2EkZW1vdGlvbl9pZHgpCnRwci5mcHIubGRhIDwtIFdlaWdodGVkUk9DKGFzLm51bWVyaWMocHJlZF9sZGEpLCBsYWJlbF9sZGEpCmF1Y19sZGEgPC0gV2VpZ2h0ZWRBVUModHByLmZwci5sZGEpCgpjYXQoIlRoZSBhY2N1cmFjeSBvZiB0aGUgUENBK0xEQSBtb2RlbDoiLCAiaXMiLCBhY2N1X2xkYSoxMDAsICIlLlxuIikKY2F0KCJUaGUgQVVDIG9mIHRoZSBQQ0ErTERBIG1vZGVsOiIsICJpcyIsIGF1Y19sZGEsICIuXG4iKQpgYGAKCiMjIyBTdW1tYXJpemUgUnVubmluZyBUaW1lClByZWRpY3Rpb24gcGVyZm9ybWFuY2UgbWF0dGVycywgc28gZG9lcyB0aGUgcnVubmluZyB0aW1lcyBmb3IgY29uc3RydWN0aW5nIGZlYXR1cmVzIGFuZCBmb3IgdHJhaW5pbmcgdGhlIG1vZGVsLCBlc3BlY2lhbGx5IHdoZW4gdGhlIGNvbXB1dGF0aW9uIHJlc291cmNlIGlzIGxpbWl0ZWQuIAoKKkJhc2VsaW5lIE1vZGVsCmBgYHtyIHJ1bm5pbmdfdGltZV9iYXNlbGluZX0KY2F0KCJUaW1lIGZvciBjb25zdHJ1Y3RpbmcgdHJhaW5pbmcgZmVhdHVyZXM9IiwgdG1fZmVhdHVyZV90cmFpblsxXSwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0ZXN0aW5nIGZlYXR1cmVzPSIsIHRtX2ZlYXR1cmVfdGVzdFsxXSwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIHRyYWluaW5nIG1vZGVsPSIsIHRtX3RyYWluX2Jhc2VsaW5lWzFdLCAicyBcbiIpIApjYXQoIlRpbWUgZm9yIHRlc3RpbmcgbW9kZWw9IiwgdG1fdGVzdF9iYXNlbGluZVsxXSwgInMgXG4iKQpgYGAKCipTVk0KYGBge3IgcnVubmluZ190aW1lX1NWTX0KY2F0KCJUaW1lIGZvciBjb25zdHJ1Y3RpbmcgdHJhaW5pbmcgZmVhdHVyZXM9IiwgdG1fZmVhdHVyZV90cmFpblsxXSwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0ZXN0aW5nIGZlYXR1cmVzPSIsIHRtX2ZlYXR1cmVfdGVzdFsxXSwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIHRyYWluaW5nIG1vZGVsPSIsIHRtX3RyYWluX3N2bVsxXSwgInMgXG4iKSAKY2F0KCJUaW1lIGZvciB0ZXN0aW5nIG1vZGVsPSIsIHRtX3Rlc3Rfc3ZtWzFdLCAicyBcbiIpCmBgYAoKKlhHQm9vc3QKYGBge3IgcnVubmluZ190aW1lX1hHQn0KY2F0KCJUaW1lIGZvciBjb25zdHJ1Y3RpbmcgdHJhaW5pbmcgZmVhdHVyZXM9IiwgdG1fZmVhdHVyZV90cmFpblsxXSwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0ZXN0aW5nIGZlYXR1cmVzPSIsIHRtX2ZlYXR1cmVfdGVzdFsxXSwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIHRyYWluaW5nIG1vZGVsPSIsIHhnYl90cmFpbl90aW1lWzFdLCAicyBcbiIpIApjYXQoIlRpbWUgZm9yIHRlc3RpbmcgbW9kZWw9IiwgdG1fdGVzdF94Z2JbMV0sICJzIFxuIikKCmBgYAoKKlJhbmRvbSBGb3Jlc3QKYGBge3IgcnVubmluZ190aW1lX3JmfQpjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0cmFpbmluZyBmZWF0dXJlcz0iLCB0bV9mZWF0dXJlX3RyYWluWzFdLCAicyBcbiIpCmNhdCgiVGltZSBmb3IgY29uc3RydWN0aW5nIHRlc3RpbmcgZmVhdHVyZXM9IiwgdG1fZmVhdHVyZV90ZXN0WzFdLCAicyBcbiIpCmNhdCgiVGltZSBmb3IgdHJhaW5pbmcgcmFuZG9tIGZvcmVzdCBtb2RlbD0iLCByZl90cmFpbl90aW1lWzFdLCAicyBcbiIpCmNhdCgiVGltZSBmb3IgdGVzdGluZyByYW5kb20gZm9yZXN0IG1vZGVsPSIsIHRtX3Rlc3RfcmZbMV0sICJzIFxuIikKYGBgCgoqIFBDQStMREEKYGBge3IgcnVubmluZ190aW1lX1BDQStMREF9CmNhdCgiVGltZSBmb3IgY29uc3RydWN0aW5nIHRyYWluaW5nIGZlYXR1cmVzPSIsIHRtX2ZlYXR1cmVfdHJhaW5bMV0sICJzIFxuIikKY2F0KCJUaW1lIGZvciBjb25zdHJ1Y3RpbmcgdGVzdGluZyBmZWF0dXJlcz0iLCB0bV9mZWF0dXJlX3Rlc3RbMV0sICJzIFxuIikKCmNhdCgiVGltZSBmb3IgdHJhaW5pbmcgUENBID0iLCB0bV90cmFpbl9wY2FbMV0sICJzIFxuIikKY2F0KCJUaW1lIGZvciB0ZXN0aW5nIFBDQSA9IiwgdG1fdGVzdF9wY2FbMV0sICJzIFxuIikKY2F0KCJUaW1lIGZvciB0cmFpbmluZyBtb2RlbCBMREEgPSAiLCB0bV90cmFpbl9sZGFbMV0sICJzIFxuIikKY2F0KCJUaW1lIGZvciB0ZXN0aW5nIG1vZGVsIExEQSA9ICIsdG1fdGVzdF9sZGFbMV0sICJzIFxuIikKYGBgCiMjI1JlZmVyZW5jZQotIER1LCBTLiwgVGFvLCBZLiwgJiBNYXJ0aW5leiwgQS4gTS4gKDIwMTQpLiBDb21wb3VuZCBmYWNpYWwgZXhwcmVzc2lvbnMgb2YgZW1vdGlvbi4gUHJvY2VlZGluZ3Mgb2YgdGhlIE5hdGlvbmFsIEFjYWRlbXkgb2YgU2NpZW5jZXMsIDExMSgxNSksIEUxNDU0LUUxNDYyLgoKCgoKCgoKCgoKCgoK